#include "evaluator.h"
#include <fstream>
#include <cstdlib>
#include <cstring>
#include <iostream>

using namespace std;

/* weights for different stages */
Weights WEIGHTS[NUM_STAGES];

/* Masks for each filled line */
BitBoard FILLED_MASK[4][19];

/* Masks for stable pieces in each direction (pieces adjacent to edge/block) */
BitBoard STABLE_MASK[MAX_DIRECTION];

/* Mask for the edges */
BitBoard EDGE_MASK;

/* Table converting each direction to the line it belongs to */
const int DIRECTION_TO_LINE[8] = {  NDG, // UP_LEFT     0
                                    COL, // UP          1
                                    PDG, // UP_RIGHT    2
                                    ROW, // LEFT        3
                                    ROW, // RIGHT       4
                                    PDG, // DOWN_LEFT   5
                                    COL, // DOWN        6
                                    NDG  // DOWN_RIGHT  7
                                 };

/* evaluate the given player's number of moves
   #in#  gs             ... current gameState
   #in#  player         ... current player
   #ret#                ... number of moves the player has                  */
int evaluateMobility(gameState gs, BoardState player, BitBoard blocked)
{
    BitBoard self, opponent, empty, result, temp;
    
    if (player == BLACK)
    {
        self = gs.black;
        opponent = gs.white;
    }
    else
    { 
        self = gs.white;
        opponent = gs.black;
    }
    empty = ~(self | opponent | blocked);
    
    result.top = result.bottom = 0;
    
    for (int i = 0; i < MAX_DIRECTION; i++)
    {   
        int length = 0;
        // temp = (self << DIRECTION_SHIFT[i]) & DIRECTION_MASK[i] & opponent;
        temp = (self << DIRECTION_SHIFT[i]);
        BIT_AND(temp, DIRECTION_MASK[i], temp);
        BIT_AND(temp, opponent, temp);
        
        while (temp != 0)
        {   
            length++; 
            // temp = (temp << DIRECTION_SHIFT[i]) & DIRECTION_MASK[i];
            temp = (temp << DIRECTION_SHIFT[i]);
            BIT_AND(temp, DIRECTION_MASK[i], temp);
            
            // BitBoard newMove = temp & empty;
            BitBoard newMove;
            BIT_AND(temp, empty, newMove);
            
            result = result | newMove;          
            // temp = temp & opponent;
            
            BIT_AND(temp, opponent, temp);
        }
    }
    
    int mobility = BitCount(result.top) + BitCount(result.bottom);
    
    return mobility;
}

/* count the number of frontier discs (discs next to empty squares)
   #in#  self           ... bitboard for the color we are evaluating
   #in#  empty          ... bitboard of empty squares
   #ret#                ... number of frontier discs                        */
int evaluateFrontier(BitBoard self, BitBoard empty)
{
    int sum = 0;
    // count the number of empty squares in each direction and add them up
    for (int i = 0; i < MAX_DIRECTION; i++)
    {
        // BitBoard temp = (self << DIRECTION_SHIFT[i]) & DIRECTION_MASK[i] & empty;
        BitBoard temp = (self << DIRECTION_SHIFT[i]);
        BIT_AND(temp, DIRECTION_MASK[i], temp);
        BIT_AND(temp, empty, temp);
        
        sum += BitCount(temp.top) + BitCount(temp.bottom);   
        
        // self = self & ~(temp >> DIRECTION_SHIFT[i]);
        temp = ~(temp >> DIRECTION_SHIFT[i]);
        BIT_AND(self, temp, self);
    }
    return sum;
}

/* calculate the how many more pieces white has over black */
int evaluatePieceDiff(gameState gs)
{
    return BitCount(gs.white.top) + BitCount(gs.white.bottom)
           - BitCount(gs.black.top) - BitCount(gs.black.bottom);
}

/* evaluate the gameState
   #in#  gs             ... current gameState
   #in#  blocked        ... map for blocked squares
   #in#  player         ... current player
   #ret#                ... integer value of the gameState,
                            positive means favouring WHITE                  */
int evaluateGame(gameState gs, BitBoard blocked, BoardState player, int stage)
{
    // wipe out avoidance
    if (BitCount(gs.black) <= 1 && stage > 4)
        return WIN_WEIGHT / 4;
        
    Feature_Values feature_value = GetFeatureValue(gs, player, blocked);
    
    return (int) evaluateGameL(feature_value, WEIGHTS[stage]);
}

int evaluateTerminal(gameState gs)
{
    int disc_diff = evaluatePieceDiff(gs);
    
    if (disc_diff > 0)
        return disc_diff * TERMINAL_DIFF_WEIGHT + WIN_WEIGHT;
    else if (disc_diff < 0)
        return disc_diff * TERMINAL_DIFF_WEIGHT - WIN_WEIGHT;
    else
        return 0;
}

Feature_Values GetFeatureValue(gameState gs, BoardState player, BitBoard blocked)
{
    BitBoard empty = ~(gs.black | gs.white | blocked);
    
    Feature_Values v;
    
    // mobility and frontier discs
    int m_black = evaluateMobility(gs, BLACK, blocked);
    int m_white = evaluateMobility(gs, WHITE, blocked);
    v.mobility = m_white - m_black;
    v.frontier = evaluateFrontier(gs.white, empty) - evaluateFrontier(gs.black, empty);
    
    // player turn and bias
    v.player = player == WHITE ? 1 : -1;
    v.bias = 1;
    
    // stablility and dics diff
    v.disc_diff = evaluatePieceDiff(gs);
    v.stability = evaluateStabilityDiff(gs, blocked, v.edge_stab, v.flippable);
    
    for (int i = 0; i < 15; i++)
    {
        v.disc_feature[i] = BitCount(gs.white & DISC_FEATURE[i]) - BitCount(gs.black & DISC_FEATURE[i]);
    }
    
    return v;
}

/* evaluate difference between number of stable discs
   #in#  gs             ... current gameState
   #in#  blocked        ... map for the blocked squares
   #ret#                ... white_stable - black_stable                     */
int evaluateStabilityDiff(gameState gs, BitBoard blocked, int & edge_stab, int & flippable)
{
    edge_stab = 0;
    // initialise filled and empty boards
    BitBoard filled = gs.black | gs.white | blocked;
    BitBoard empty = ~filled;
    
    // a bit board for stable pieces in each direction
    // {row, colunm, negative diagonal, positive diagonal}    
    BitBoard whiteStable[MAX_LINE_TYPE];
    BitBoard blackStable[MAX_LINE_TYPE];
    
    // initialise stable pieces to empty
    memset(whiteStable, 0, sizeof(BitBoard)*MAX_LINE_TYPE);
    memset(blackStable, 0, sizeof(BitBoard)*MAX_LINE_TYPE);   
    
    // for each row/column/diagonal, if it is completely filled, then all pieces 
    // are stable in the respective direction
    for (int i = 0; i < MAX_LINE_TYPE; i++)
    {
        int max_it = i < 2 ? 10 : 19;
        for (int j = 0; j < max_it; j++)
        {
            BitBoard temp;
            BIT_AND(filled, FILLED_MASK[i][j], temp);
            if (temp == FILLED_MASK[i][j])
            {
                whiteStable[i] = whiteStable[i] | (gs.white & FILLED_MASK[i][j]);
                blackStable[i] = blackStable[i] | (gs.black & FILLED_MASK[i][j]);
            }
        }
    }
    
    // evaluate the rest of the stable pieces by induction
    // base case: a piece next to an edge/block in one direction is stable in
    //            that direction
    // induction: a piece next to a stable piece of the same color in one direction
    //            is stable in that direction
    for (int dir = 0; dir < MAX_DIRECTION; dir++)
    {
        BitBoard t1, t2;
        BitBoard newWhite;
        t1 = ~whiteStable[DIRECTION_TO_LINE[dir]];
        BIT_AND(gs.white, STABLE_MASK[dir], newWhite);
        BIT_AND(newWhite, t1, newWhite);
        
        while (newWhite != 0)
        {   
            whiteStable[DIRECTION_TO_LINE[dir]] |= newWhite;
            // newWhite = gs.white & SHIFT(newWhite, dir) & ~whiteStable[DIRECTION_TO_LINE[dir]];
            t1 = SHIFT(newWhite, dir);
            t2 = ~whiteStable[DIRECTION_TO_LINE[dir]];
            BIT_AND(gs.white, t1, newWhite);
            BIT_AND(newWhite, t2, newWhite);
        }
    }
    for (int dir = 0; dir < MAX_DIRECTION; dir++)
    {
        // BitBoard newBlack = gs.black & STABLE_MASK[dir] & ~blackStable[DIRECTION_TO_LINE[dir]];
        BitBoard newBlack, t1, t2;
        t1 = ~blackStable[DIRECTION_TO_LINE[dir]];
        BIT_AND(gs.black, STABLE_MASK[dir], newBlack);
        BIT_AND(newBlack, t1, newBlack);
        
        while (newBlack != 0)
        {   
            blackStable[DIRECTION_TO_LINE[dir]] |= newBlack; 
            //newBlack = gs.black & SHIFT(newBlack, dir) & ~blackStable[DIRECTION_TO_LINE[dir]];
            
            t1 = SHIFT(newBlack, dir);
            t2 = ~blackStable[DIRECTION_TO_LINE[dir]];
            BIT_AND(gs.black, t1, newBlack);
            BIT_AND(newBlack, t2, newBlack);
        }
    }
    BitBoard wStable = whiteStable[0] & whiteStable[1] & whiteStable[2] & whiteStable[3];
    BitBoard bStable = blackStable[0] & blackStable[1] & blackStable[2] & blackStable[3];
    
    edge_stab = BitCount(wStable & EDGE_MASK) - BitCount(bStable & EDGE_MASK);
    
    BitBoard r1 = {0, 0};
    BitBoard r2 = {0, 0};
    for (int i = 0; i < 4; i++)
    {
        BitBoard t1 = {-1, -1};
        BitBoard t2 = {-1, -1};
        for (int j = 0; j < 4; j++)
        {
            if (i == j)
                continue;
            BIT_AND(t1, whiteStable[j], t1);
            BIT_AND(t2, blackStable[j], t2);
        }
        r1 = r1 | t1;
        r2 = r2 | t1;
    }
    
    flippable = BitCount(r1) - BitCount(r2);
    
    return BitCount(wStable) - BitCount(bStable);
}

/* evaluate the gameState using pre-computed feature values */
float evaluateGameL(Feature_Values values, Weights weights)
{
    float result = 0;
    float * w = (float*) &weights;
    int * v = (int*) &values;
    
    for (int i = 0; i < NUM_FEATURES; i++)
    {
        result += w[i] * v[i];
    }
    return result;
}

void initialiseBoardSpecific(BitBoard blocked)
{   
    // initialise stable positions
    // going up = bottom row + above a block
    STABLE_MASK[UP] = FILLED_MASK[ROW][9] | ((blocked << DIRECTION_SHIFT[UP]) & DIRECTION_MASK[UP]);
    // going down = top row + below a block
    STABLE_MASK[DOWN] = FILLED_MASK[ROW][0] | ((blocked << DIRECTION_SHIFT[DOWN]) & DIRECTION_MASK[DOWN]);
    // going right = left column + right of a block
    STABLE_MASK[RIGHT] = FILLED_MASK[COL][0] | ((blocked << DIRECTION_SHIFT[RIGHT]) & DIRECTION_MASK[RIGHT]);
    // going left = right column + left of a block
    STABLE_MASK[LEFT] = FILLED_MASK[COL][9] | ((blocked << DIRECTION_SHIFT[LEFT]) & DIRECTION_MASK[LEFT]);
    
    // up left = bottom row + right column + up left of a block
    STABLE_MASK[UP_LEFT] = FILLED_MASK[COL][9] | FILLED_MASK[ROW][9] | SHIFT(blocked, UP_LEFT);
    // up right
    STABLE_MASK[UP_RIGHT] = FILLED_MASK[COL][0] | FILLED_MASK[ROW][9] | SHIFT(blocked, UP_RIGHT);
    // down left
    STABLE_MASK[DOWN_LEFT] = FILLED_MASK[COL][9] | FILLED_MASK[ROW][0] | SHIFT(blocked, DOWN_LEFT);
    // down right
    STABLE_MASK[DOWN_RIGHT] = FILLED_MASK[COL][0] | FILLED_MASK[ROW][0] | SHIFT(blocked, DOWN_RIGHT);
}

/* initialise weights by reading from a file
   set up memory used to store the game information                         */
void initialiseEvaluator()
{
    // assign random weights if weights cannot be read from file
    if (!ReadWeights(WEIGHTS, WEIGHT_FILE))
    {
        for (int i = 0; i < NUM_STAGES - 1; i++)
        {
            float * w = (float*) &WEIGHTS[i];
            for (int j = 0; j < NUM_FEATURES; j++)
            {
	            w[j] = 0; 
            }
        }
        float * w1 = (float*) &WEIGHTS[NUM_STAGES-1];
        for (int i = 0; i < NUM_FEATURES; i++)
        {
            w1[i] = i;
        }
    }
    
    // initialise mask for filled lines
    for (int i = 0; i < 19; i++)
    {
        // 10 rows and columns
        if (i < 10)
        {
            BitBoard temp = {1, 0};
            BitBoard row = temp << (i * 10);
            BitBoard column = temp << i;
            
            for (int j = 0; j < 9; j++)
            {
                row = row | (row << 1);
                column = column | (column << 10);
            }
            FILLED_MASK[ROW][i] = row;
            FILLED_MASK[COL][i] = column;
        }
        
        // 18 diagonals
        {
            BitBoard pos = {1, 0};
            if (i < 10)
                pos = pos << (i * 10);
            else
                pos = pos << (90 + i - 9);
                
            BitBoard neg = {1, 0};
            neg = neg << 90;
            if (i < 10)
                neg = neg >> (i * 10);
            else
                neg = neg >> (90 + 9 - i);
                
            for (int j = 0; j < 10; j++)
            {
                pos = pos | (pos << DIRECTION_SHIFT[UP_RIGHT]) & DIRECTION_MASK[UP_RIGHT];
                neg = neg | (neg << DIRECTION_SHIFT[DOWN_RIGHT]) & DIRECTION_MASK[DOWN_RIGHT];
            }
            FILLED_MASK[PDG][i] = pos;
            FILLED_MASK[NDG][i] = neg;
        }
    }
    
    EDGE_MASK = FILLED_MASK[ROW][0] | FILLED_MASK[ROW][9] | FILLED_MASK[COL][0] | FILLED_MASK[COL][9];
}

/* read weights from file, return false if failed */
bool ReadWeights(Weights weight[], const char * file)
{
    fstream filestr;
    
    filestr.open(file, fstream::in);
    
    if (filestr.is_open())
    {
        char buffer[100];
        
        for (int i = 0; i < NUM_FEATURES; i++)
        {
            for (int j = 0; j < NUM_STAGES; j++)
            {
                float * w = (float*)&weight[j];
                filestr.getline(buffer, 100);
                w[i] = atof(buffer);    
            }
        } 
        filestr.close();
        return true;
    }
    
    return false;
}

/* set weight and get weight functions */
void SetWeights(Weights in_weights[])
{
    for (int i = 0; i < NUM_STAGES; i++)
        WEIGHTS[i] = in_weights[i];
}

void GetWeights(Weights out_weights[])
{
    for (int i = 0; i < NUM_STAGES; i++)
        out_weights[i] = WEIGHTS[i];
}

/* write weights back into file */
void WriteWeights(const char * filename)
{
    fstream filestr;
    if (filename == NULL)
        filestr.open(WEIGHT_FILE, fstream::out);   
    else
        filestr.open(filename, fstream::out);
    
    if(filestr.is_open())
    {
        for (int i = 0; i < NUM_FEATURES; i++)
        {
            for (int j = 0; j < NUM_STAGES; j++)
            {
                float * w = (float*) &WEIGHTS[j];
                filestr << w[i] << endl;
            }
        }
    }
    filestr.close();
}

Weights operator-(Weights w1, Weights w2)
{
    Weights w3;
    
    float * p1 = (float*) &w1;
    float * p2 = (float*) &w2;
    float * p3 = (float*) &w3;
    
    for (int i = 0; i < NUM_FEATURES; i++)
    {
        p3[i] = p1[i] - p2[i];
    }
    return w3;
}

Weights operator+(Weights w1, Weights w2)
{
    Weights w3;
    
    float * p1 = (float*) &w1;
    float * p2 = (float*) &w2;
    float * p3 = (float*) &w3; 
    
    for (int i = 0; i < NUM_FEATURES; i++)
    {
        p3[i] = p1[i] + p2[i];
    }
    return w3;
}

Weights operator*(float c, Weights w1)
{
    Weights w2;
    
    float * p1 = (float*) &w1;
    float * p2 = (float*) &w2;
    
    for (int i = 0; i < NUM_FEATURES; i++)
    {
        p2[i] = c * p1[i];
    }
    
    return w2;
}
